package org.helgoboss.scala_additions

object PathPattern {
    /**
     * Automatically applied by the compiler in case a PathPattern is expected as argument but a String is given because it's 
     * in the companion object. No import of this implicit method necessary! Is that called a view?
     */
    implicit def string2PathPattern(expression: String) = PathPattern(expression)

    sealed abstract class MatchResult
    case object NeverMatches extends MatchResult
    case object Matches extends MatchResult
    case object CanMatchSubPath extends MatchResult
    case object WillMatchEverySubPath extends MatchResult
}

case class PathPattern(expression: String) {
    import PathPattern._

    private val path = Path(expression)
    
    def matchAgainst(actualPath: Path): MatchResult = {
        def componentsMatch(actualComponent: String, patternComponent: String) = {
            val patternComponentRegexString = patternComponent
                    .replaceAll("""\.""", """\.""")
                    .replaceAll("""\*""", ".*")
            actualComponent matches patternComponentRegexString
        }
    
        val patternIter = path.components.iterator
        val actualPathIter = actualPath.components.iterator
        
        var maybeStillCanMatch = true
        var matchesBecauseOfOpenEnd = false
        var canMatchSubPathBecauseOfOpenEnd = false
        
        while (maybeStillCanMatch && patternIter.hasNext && actualPathIter.hasNext) {
            val patternComponent = patternIter.next
            val actualComponent = actualPathIter.next
            
            if (patternComponent == "**") {
                /* Component-skipping pattern component. Search in next actual path components for the first one that matches the current pattern component. */
                if (patternIter.hasNext) {
                    /* The stars are inside the pattern. Examine all actual path components including the current one trying to find the first one which 
                     * matches the pattern component after the double stars
                     */
                    var patternComponentAfterDoubleStars = patternIter.next
                    var foundMatch = false
                    var nextActualComponent = actualComponent
                    var continueSearching = true
                    
                    do {
                        foundMatch = componentsMatch(nextActualComponent, patternComponentAfterDoubleStars)
                        if (actualPathIter.hasNext && !foundMatch) {
                            nextActualComponent = actualPathIter.next
                        } else {
                            continueSearching = false
                        }
                    } while (continueSearching)
                    
                    if (foundMatch) {
                        /* We found a match. Move on normally. */
                    } else {
                        /* We couldn't find a match even we looked through the complete actual path. This doesn't match. But a sub path could. */
                        canMatchSubPathBecauseOfOpenEnd = true
                    }
                } else {
                    /* This is the last pattern component. */
                    matchesBecauseOfOpenEnd = true
                }
                
            } else {
                /* Normal pattern component */
            
                /* Check whether the next actual path component matches the current pattern component */
                if (componentsMatch(actualComponent, patternComponent)) {
                    /* They match. Move on. */
                } else {
                    /* They don't match and hence cannot match in a sub tree either*/
                    maybeStillCanMatch = false
                }
            }
        }
        
        
        if (matchesBecauseOfOpenEnd) {
            /* The pattern's last component is ** and the actual path matches the pattern because of that. */
            Matches
        } else if (canMatchSubPathBecauseOfOpenEnd) {
            /* The actual path suddenly ended when looking for the right delimiter of ** in the pattern. Because of that, the pattern could match a sub path. */
            CanMatchSubPath
        } else if (maybeStillCanMatch) {
            /* All compared components match successfully. At this point, the pattern, the actual path or both have been fully examined. */
            if (!patternIter.hasNext && !actualPathIter.hasNext) {
                /* Both pattern and actual path have been fully examined. This means, it's a perfect match. */
                Matches
            } else if (!patternIter.hasNext && actualPathIter.hasNext) {
                /* Pattern has been fully examined. Actual path goes on. */
                NeverMatches
            } else { // if (patternIter.hasNext && !actualPathIter.hasNext) 
                /* Actual path has been fully examined. Pattern goes on. So the pattern could potentially match in a sub path of the actual path. */
                if (patternIter.next == "**" && !patternIter.hasNext) {
                    /* The pattern matches every sub path because the next and last pattern component is **. */
                    WillMatchEverySubPath
                } else {
                    /* The pattern could match a sub path. */
                    CanMatchSubPath
                }
            }
        } else {
            /* In at least one component, pattern and actual path don't match. That means, pattern and actual path don't match at all. */
            NeverMatches
        }
    }   

    override def toString = path.toString
}
